/*
 * Copyright (C) 2024, Ingenic Semiconductor Co.,Ltd.
 * Author: Keven <keven.ywhan@ingenic.com>
 */

#include <common.h>
#include <gpio.h>
#include <uart.h>

static void serial_setbrg(struct jz_uart *uart)
{
	unsigned int baud_div, tmp;
	baud_div = CONFIG_UART_CLK_HZ / 16 / CONFIG_UART_BAUDRATE;

	tmp = readl(&uart->lcr);
	tmp |= UART_LCR_DLAB;
	writel(tmp, &uart->lcr);

	writel((baud_div >> 8) & 0xff, &uart->dlhr_ier);
	writel(baud_div & 0xff, &uart->rbr_thr_dllr);

	tmp &= ~UART_LCR_DLAB;
	writel(tmp, &uart->lcr);
}

static int serial_tstc(struct jz_uart *uart)
{
	if (readl(&uart->lsr) & UART_LSR_DR)
		return 1;

	return 0;
}

int serial_init()
{
	struct jz_uart *uart = (struct jz_uart *)(UART0_BASE + CONFIG_UART_INDEX * 0x1000);

	switch(CONFIG_UART_INDEX) {
	case 0:
		__gpio_b_as_uart0();
		__cpm_start_uart0();
		break;
	case 1:
		__gpio_b_as_uart1();
		__cpm_start_uart1();
		break;
	default:
		__gpio_b_as_uart1();
		__cpm_start_uart1();
		break;
	}

	/* Disable port interrupts while changing hardware */
	writel(0, &uart->dlhr_ier);

	/* Disable UART unit function */
	writel(~UART_FCR_UUE, &uart->iir_fcr);

	/* Set both receiver and transmitter in UART mode (not SIR) */
	writel(~(SIRCR_RSIRE | SIRCR_TSIRE), &uart->isr);

	/*
	 * Set databits, stopbits and parity.
	 * (8-bit data, 1 stopbit, no parity)
	 */
	writel(UART_LCR_WLEN_8 | UART_LCR_STOP_1, &uart->lcr);

	/* Set baud rate */
	serial_setbrg(uart);

	/* Enable UART unit, enable and clear FIFO */
	writel(UART_FCR_UUE | UART_FCR_FE | UART_FCR_TFLS | UART_FCR_RFLS,
	       &uart->iir_fcr);

	return 0;
}

void serial_putc(const char c)
{
	struct jz_uart *uart = (struct jz_uart *)(UART0_BASE + CONFIG_UART_INDEX * 0x1000);

	if (c == '\n')
		serial_putc('\r');

	writel((char)c, &uart->rbr_thr_dllr);

	/* Wait for fifo to shift out some bytes */
	while (!((readl(&uart->lsr) & (UART_LSR_TDRQ | UART_LSR_TEMT)) == 0x60))
		;
}

void serial_puts(const char *s)
{
	while (*s) {
		if (*s == '\n') {
			serial_putc('\r');
		}
		serial_putc(*s);
		s++;
	}
}

void serial_puthex(unsigned int d)
{
        char c[12];
        unsigned char i;

        for(i = 0; i < 8; i ++) {
                c[i] = (d >> ((7 - i) * 4)) & 0xf;
                if(c[i] < 10)
                        c[i] += 0x30;
                else
                        c[i] += (0x41 - 10);
        }
        c[8] = '\n';
        c[9] = 0;
        serial_puts(c);
}


int serial_getc(int uart_idx)
{
	struct jz_uart *uart = (struct jz_uart *)(UART0_BASE + CONFIG_UART_INDEX * 0x1000);

	while (!serial_tstc(uart))
		;
	return readl(&uart->rbr_thr_dllr);
}
